﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;

// TODO: Implement a way to change the input method(pc to xbox to wp7)
// Special Thanks: David Brown
namespace RZEngine2D
{
    public enum MouseButtons
    {
        LeftButton = 0,
        MiddleButton,
        RightButton,
        XButton1,
        XButton2
    };

    public sealed class rzInput
    {
        private static readonly rzInput s_instance = new rzInput();
        public static rzInput Instance
        {
            get { return s_instance; }
        }

        private IEnumerable<Buttons> individualButtons;

		private KeyboardState m_currentKeyboardState;
		private KeyboardState m_previousKeyboardState;

		private MouseState m_currentMouseState;
		private MouseState m_previousMouseState;

		private GamePadState[] m_currentGamePadStates;
		private GamePadState[] m_previousGamePadStates;

		private int m_numConnectedGamePads; // currently connected game pads

		private rzInput()
		{
			m_currentKeyboardState = Keyboard.GetState();
			m_previousKeyboardState = m_currentKeyboardState;

			m_currentMouseState = Mouse.GetState();
			m_previousMouseState = m_currentMouseState;

			m_currentGamePadStates = new GamePadState[4];
			m_previousGamePadStates = new GamePadState[4];

			m_numConnectedGamePads = 0;

            // check for the ammount of gamepads connected
			for (int i = 0; i < 4; i++)
			{
				m_currentGamePadStates[i] = GamePad.GetState((PlayerIndex)i);
				m_previousGamePadStates[i] = m_currentGamePadStates[i];

				if (m_currentGamePadStates[i].IsConnected)
					m_numConnectedGamePads++;
			}

            // populate our list with all possible buttons. we use LINQ and reflection instead of
            // Enum.GetValues since Enum.GetValues isn't available on Xbox.
            individualButtons =
                from x in typeof(Buttons).GetFields(BindingFlags.Static | BindingFlags.Public)
                select (Buttons)x.GetValue(null);
		}

		/// <summary>
		/// Reads the states of attached input devices.
		/// NOTE: Should usually only be called ONCE per frame.
		/// </summary>
		public void ReadDevices()
		{
			m_previousKeyboardState = m_currentKeyboardState;
			m_currentKeyboardState = Keyboard.GetState();

			m_previousMouseState = m_currentMouseState;
			m_currentMouseState = Mouse.GetState();

			m_numConnectedGamePads = 0;

			for (int i = 0; i < 4; i++)
			{
				m_previousGamePadStates[i] = m_currentGamePadStates[i];
				m_currentGamePadStates[i] = GamePad.GetState((PlayerIndex)i);

				if (m_currentGamePadStates[i].IsConnected)
					m_numConnectedGamePads++;
			}
		}

		///////////////////////////////////////////////////////////////
		//  Keyboard

		/// <summary>
		/// Checks if a key is currently down.
		/// </summary>
		/// <param name="key">The keyboard key to check.</param>
		/// <returns>true if the key is currently down.</returns>
		public bool KeyDown(Keys key)
		{
			return m_currentKeyboardState.IsKeyDown(key);
		}

		/// <summary>
		/// Checks if a key is currently up.
		/// </summary>
		/// <param name="key">The keyboard key to check.</param>
		/// <returns>true if the key is currently up.</returns>
		public bool KeyUp(Keys key)
		{
			return m_currentKeyboardState.IsKeyUp(key);
		}

		/// <summary>
		/// Checks if a key was pressed this frame.
		/// </summary>
		/// <param name="key">The keyboard key to check.</param>
		/// <returns>true if the key was pressed this frame.</returns>
		public bool KeyPressed(Keys key)
		{
			return m_currentKeyboardState.IsKeyDown(key) && m_previousKeyboardState.IsKeyUp(key);
		}

		/// <summary>
		/// Checks if a key was released this frame.
		/// </summary>
		/// <param name="key">The keyboard key to check.</param>
		/// <returns>true if the key was released this frame.</returns>
		public bool KeyReleased(Keys key)
		{
			return m_currentKeyboardState.IsKeyUp(key) && m_previousKeyboardState.IsKeyDown(key);
		}

		//////////////////////////////////////////////////////////////
		//  Mouse

		/// <summary>
		/// Reports if a button is down in the passed in state.
		/// </summary>
		/// <param name="state">The state to check.</param>
		/// <param name="btn">The button to check.</param>
		/// <returns>true if the button is currently down in the passed in state.</returns>
		private bool IsMouseButtonDown(MouseState state, MouseButtons btn)
		{
			bool buttonDown = false;

			switch (btn)
			{
			case MouseButtons.LeftButton:
				buttonDown = (state.LeftButton == ButtonState.Pressed);
				break;
			case MouseButtons.MiddleButton:
				buttonDown = (state.MiddleButton == ButtonState.Pressed);
				break;
			case MouseButtons.RightButton:
				buttonDown = (state.RightButton == ButtonState.Pressed);
				break;
			case MouseButtons.XButton1:
				buttonDown = (state.XButton1 == ButtonState.Pressed);
				break;
			case MouseButtons.XButton2:
				buttonDown = (state.XButton2 == ButtonState.Pressed);
				break;
			}
			return buttonDown;
		}

		/// <summary>
		/// Returns the mouse position as a Point for ease of use.
		/// </summary>
		/// <returns>The current mouse position as a Point.</returns>
		public Point MousePoint
		{
			get { return new Point(m_currentMouseState.X, m_currentMouseState.Y); }
		}

        /// <summary>
        /// Returns the mouse position as a Point for ease of use.
        /// </summary>
        /// <returns>The current mouse position as a Vector2.</returns>
        public Vector2 MousePosition
        {
            get { return new Vector2(m_currentMouseState.X, m_currentMouseState.Y); }
        }

        /// <summary>
        /// Returns the amount the mouse position changed from the previous frame.
        /// </summary>
        /// <returns>The mouse movement as a Vector2.</returns>
        public Vector2 MouseDelta
        {
            get { return new Vector2(m_currentMouseState.X - m_previousMouseState.X, m_currentMouseState.Y - m_previousMouseState.Y); }
        }

		/// <summary>
		/// Checks if a mouse button is currently down.
		/// </summary>
		/// <param name="btn">The button to check.</param>
		/// <returns>true if the button is currently down.</returns>
		public bool MouseButtonDown(MouseButtons btn)
		{
			return IsMouseButtonDown(m_currentMouseState, btn);
		}

		/// <summary>
		/// Checks if a mouse button is currently up.
		/// </summary>
		/// <param name="btn">The button to check.</param>
		/// <returns>true if the button is currently up.</returns>
		public bool MouseButtonUp(MouseButtons btn)
		{
			return !IsMouseButtonDown(m_currentMouseState, btn);
		}

		/// <summary>
		/// Checks if a mouse button was pressed this frame.
		/// </summary>
		/// <param name="btn">The mouse button to check.</param>
		/// <returns>true if the mouse button was pressed this frame.</returns>
		public bool MouseButtonPressed(MouseButtons btn)
		{
			return IsMouseButtonDown(m_currentMouseState, btn) && !IsMouseButtonDown(m_previousMouseState, btn);
		}

		/// <summary>
		/// Checks if a mouse button was released this frame.
		/// </summary>
		/// <param name="btn">The mouse button to check.</param>
		/// <returns>true if the mouse button was released this frame.</returns>
		public bool MouseButtonReleased(MouseButtons btn)
		{
			return !IsMouseButtonDown(m_currentMouseState, btn) && IsMouseButtonDown(m_previousMouseState, btn);
		}

		/// <summary>
		/// Reports how much the mouse wheel has changed this frame.
		/// </summary>
		/// <returns>The difference between the current and previous mouse wheel values.</returns>
		public int MouseWheelDelta()
		{
			return m_currentMouseState.ScrollWheelValue - m_previousMouseState.ScrollWheelValue;
		}

		//////////////////////////////////////////////////////////////
		//  GamePad

		////////////////////////////////////////////////////////////
		//  Buttons (includes DPad and thumbstick buttons)

		/// <summary>
		/// Checks if a button is down.
		/// </summary>
		/// <param name="player">Which player to report.</param>
		/// <param name="button">The button to check.</param>
		/// <returns>true if the button is down.</returns>
		public bool ButtonDown(PlayerIndex player, Buttons button)
		{
			//  If gamepad is unplugged, return false
			if (IsGamePadConnected(player) == false)
				return false;

			return m_currentGamePadStates[(int)player].IsButtonDown(button);
		}

		/// <summary>
		/// Checks if a button is up.
		/// </summary>
		/// <param name="player">Which player to report.</param>
		/// <param name="button">The button to check.</param>
		/// <returns>true if the button is up.</returns>
		public bool ButtonUp(PlayerIndex player, Buttons button)
		{
			//  If gamepad is unplugged, return false
			if (IsGamePadConnected(player) == false)
				return false;

			return m_currentGamePadStates[(int)player].IsButtonUp(button);
		}

		/// <summary>
		/// Checks if a certain player pressed a button this frame.
		/// </summary>
		/// <param name="player">Which player to report.</param>
		/// <param name="button">The button to check.</param>
		/// <returns>true if the button was pressed this frame.</returns>
		public bool ButtonPressed(PlayerIndex player, Buttons button)
		{
			//  If gamepad is unplugged, return false
			if (IsGamePadConnected(player) == false)
				return false;

			return m_currentGamePadStates[(int)player].IsButtonDown(button) &&
				   m_previousGamePadStates[(int)player].IsButtonUp(button);
		}

		/// <summary>
		/// Checks if a certain player released a button this frame.
		/// </summary>
		/// <param name="player">Which player to report.</param>
		/// <param name="button">The button to check.</param>
		/// <returns>true if the button was released this frame.</returns>
		public bool ButtonReleased(PlayerIndex player, Buttons button)
		{
			//  If gamepad is unplugged, return false
			if (IsGamePadConnected(player) == false)
				return false;

			return m_currentGamePadStates[(int)player].IsButtonUp(button) &&
				   m_previousGamePadStates[(int)player].IsButtonDown(button);
		}

        /// <summary>
        /// Determines if any of the buttons specified are currently pressed.
        /// </summary>
        /// <param name="player">Which player to report.</param>
        /// <param name="buttons">One or more buttons to test for.</param>
        /// <returns>True if any of the buttons are pressed, false otherwise.</returns>
        public bool AreAnyButtonsDown(PlayerIndex player, Buttons buttons)
        {
            //  If gamepad is unplugged, return false
            if (IsGamePadConnected(player) == false)
                return false;

            // iterate through all the possible buttons
            foreach (var button in individualButtons)
            {
                // if the parameter contains the button and the state has the button as pressed,
                // return true
                if ((buttons & button) != 0 && m_currentGamePadStates[(int)player].IsButtonDown(button))
                    return true;
            }

            // if we're here, none of the requested buttons were pressed
            return false;
        }

		////////////////////////////////////////////////////////////
		//  Triggers

		/// <summary>
		/// Gets the current state of the trigger for the player.
		/// </summary>
		/// <param name="player">Which player to report.</param>
		/// <returns>0.0 (not pressed) to 1.0 (fully pressed)</returns>
		public float LeftTrigger(PlayerIndex player)
		{
			//  If gamepad is unplugged, return 0.0f
			if (IsGamePadConnected(player) == false)
				return 0.0f;

			return m_currentGamePadStates[(int)player].Triggers.Left;
		}

		/// <summary>
		/// Gets the current state of the trigger for the player.
		/// </summary>
		/// <param name="player">Which player to report.</param>
		/// <returns>0.0 (not pressed) to 1.0 (fully pressed)</returns>
		public float RightTrigger(PlayerIndex player)
		{
			//  If gamepad is unplugged, return 0.0f
			if (IsGamePadConnected(player) == false)
				return 0.0f;

			return m_currentGamePadStates[(int)player].Triggers.Right;
		}

		////////////////////////////////////////////////////////////
		//  Thumbsticks

		/// <summary>
		/// Gets the current state of the thumbstick for the player.
		/// </summary>
		/// <param name="player">Which player to report.</param>
		/// <returns>-1.0 to 1.0 for each axis.</returns>
		public Vector2 LeftThumbstick(PlayerIndex player)
		{
			//  If gamepad is unplugged, return Vector2.Zero
			if (IsGamePadConnected(player) == false)
				return Vector2.Zero;

			return m_currentGamePadStates[(int)player].ThumbSticks.Left;
		}

        /// <summary>
        /// Reports if the thumbstick was just pressed this frame.
        /// </summary>
        /// <param name="player">Which player to report.</param>
        /// <returns>-1.0 to 1.0 for each axis ONLY IF PRESSED THIS FRAME.</returns>
        public Vector2 LeftThumbstickPressed(PlayerIndex player)
        {
            //  If gamepad is unplugged, return Vector2.Zero
            if (IsGamePadConnected(player) == false)
                return Vector2.Zero;

            Vector2 prevPressed = m_previousGamePadStates[(int)player].ThumbSticks.Left;

            Vector2 pressed = m_currentGamePadStates[(int)player].ThumbSticks.Left;

            if (prevPressed.X != 0.0f)
                pressed.X = 0.0f;

            if (prevPressed.Y != 0.0f)
                pressed.Y = 0.0f;

            return pressed;
        }

		/// <summary>
		/// Gets the current state of the thumbstick for the player.
		/// </summary>
		/// <param name="player">Which player to report.</param>
		/// <returns>-1.0 to 1.0 for each axis.</returns>
		public Vector2 RightThumbstick(PlayerIndex player)
		{
			//  If gamepad is unplugged, return Vector2.Zero
			if (IsGamePadConnected(player) == false)
				return Vector2.Zero;

			return m_currentGamePadStates[(int)player].ThumbSticks.Right;
		}

        /// <summary>
        /// Reports if the thumbstick was just pressed this frame.
        /// </summary>
        /// <param name="player">Which player to report.</param>
        /// <returns>-1.0 to 1.0 for each axis ONLY IF PRESSED THIS FRAME.</returns>
        public Vector2 RightThumbstickPressed(PlayerIndex player)
        {
            //  If gamepad is unplugged, return Vector2.Zero
            if (IsGamePadConnected(player) == false)
                return Vector2.Zero;

            Vector2 prevPressed = m_previousGamePadStates[(int)player].ThumbSticks.Right;

            Vector2 pressed = m_currentGamePadStates[(int)player].ThumbSticks.Right;

            if (prevPressed.X != 0.0f)
                pressed.X = 0.0f;

            if (prevPressed.Y != 0.0f)
                pressed.Y = 0.0f;

            return pressed;
        }

		///////////////////////////////////////////////////////
		//  Connected (or not)

		/// <summary>
		/// Checks if a gamepad is currently connected.
		/// </summary>
		/// <param name="player">Which player to report.</param>
		/// <returns>true if the gamepad is currently connected.</returns>
		public bool IsGamePadConnected(PlayerIndex player)
		{
			return m_currentGamePadStates[(int)player].IsConnected;
		}

		/// <summary>
		/// Returns the number of currently connected gamepads.
		/// </summary>
		public int ConnectedGamePads
		{
			get { return m_numConnectedGamePads; }
		}
    }
}
